﻿using Microsoft.Extensions.DependencyInjection;
using NPOI.SS.Formula.Functions;
using S7.Net;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Zocono.Infrastructure.Common;

namespace Zocono.WCS.Infrastructure.PlcCommon
{
    public class PlcConnectionS7
    {
        private readonly ILogUtil _LogUtil;
        private Plc _Plc;
        public long PlcConnectionID { get; set; }
        public bool IsConnected { get { return _Plc == null ? false : _Plc.IsConnected; } }
        public string IP { get; set; }

        public bool IsSc { get; set; }
        public string ScEquipmentCode { get; set; }
        private CpuType _cpuType;
        private short _rack;
        private short _slot;
        public PlcConnectionS7(CpuType cpuType, string ip, short rack, short slot, long plcConnectionID, bool isSc, string scEquipmentCode, ILogUtil logUtil)
        {
            //slot:PLC的CPU插槽，通常S7300-S7400为2,S7-1200和S7-1500为0。如果您使用外部以太网卡，则必须相应地进行设置。
            //rack:通常是0，但请检查Step7或TIA portal的硬件配置
            _Plc = new Plc(cpuType, ip, rack, slot);
            PlcConnectionID = plcConnectionID;
            IsSc = isSc;
            IP = ip;
            ScEquipmentCode = scEquipmentCode;
            _LogUtil = logUtil;
            _Plc.ReadTimeout = 2000;
            _Plc.WriteTimeout = 2000;
            _cpuType = cpuType;
            _rack = rack;
            _slot = slot;
        }
        public async Task<bool> ConnectAsync()
        {
            if (IsConnected)
            {
                return true;
            }

            try
            {
                bool _isConnected = await Zocono.Infrastructure.Common.CommunicationHelper.IPService.PingAsync(_Plc.IP);
                if (_isConnected)
                {
                    _LogUtil.Info($"ping plc [{_Plc.IP}] OK ，开始连接plc; [{DateTime.Now}]");
                    await _Plc.OpenAsync();//连接
                    _LogUtil.Info($"plc [{_Plc.IP}]  ，连接成功; [{DateTime.Now}]");
                }
                else
                {
                    _LogUtil.Error($"plc [{_Plc.IP}]  ，网络PING不通，请检查网络！; [{DateTime.Now}]");
                }
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc连接发生异常ConnectAsync，IP：{_Plc.IP}, ex= {ex.Message}");
            }
            return IsConnected;
        }
        public bool DisConnect()
        {
            try
            {
                _Plc.Close();
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc关闭连接异常DisConnect，IP：{IP}, ex= {ex.Message}");
            }
            return _Plc == null || _Plc.IsConnected;
        }



        public async Task<object> ReadTypeValue(string point, string type, int start = 2)
        {

            try
            {
                switch (type)
                {
                    case "bool":
                        return await ReadBool(point);
                    case "string":
                        return await ReadString(point, start);
                }
            }
            catch (Exception)
            {
                return null;
            }
            return null;

        }


        public async Task<bool> ReadBool(string point)
        {
            var value = await _Plc.ReadAsync(point);
            return value.ToBool();
        }

        public async Task<string> ReadString(string point, int start)
        {
            byte[] value = (byte[])await _Plc.ReadAsync(point);
            int length = StringExt.GetSIntAt(value, 1);
            var code = StringExt.GetCharsAt(value, start, length);
            return code;
        }

        public async Task<int> ReadInt16(string point)
        {
            var value = await _Plc.ReadAsync(point);
            return value.ToInt32();
        }

        public async Task<byte[]> ReadByte(string point)
        {
            byte[] value = (byte[])await _Plc.ReadAsync(point);
            return value;
        }

        public async Task<object> ReadPlcPoint(string point)
        {
            var value = await _Plc.ReadAsync(point);
            return value;
        }
        /// <summary>
        /// 监控读DB专用方法，尝试查找读DB方法不出来的问题
        /// </summary>
        /// <param name="DB"></param>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public async Task<byte[]> ReadDbForMonitorAsync(int DB, int start, int length, NlogUtilScoped nlogUtilScoped)
        {
            if (!await ConnectAsync())
            {
                return null;
            }
            Stopwatch stopwatch = Stopwatch.StartNew();
            stopwatch.Start();
            try
            {
                DateTime dt = DateTime.Now;
                bool isFinish = false;
                int timeout = 3;
                byte[] bytes = null;
                Thread thread = new Thread(new ThreadStart(async () =>
                {
                    try
                    {
                        using (var cts = new CancellationTokenSource(1000 * timeout))
                        {
                            bytes = await _Plc.ReadBytesAsync(DataType.DataBlock, DB, start, length, cts.Token);
                            nlogUtilScoped.Debug($"_Plc.ReadBytesAsync时长:{Math.Round(stopwatch.Elapsed.TotalMilliseconds,0)}毫秒，IP：{_Plc.IP}，DB：{DB}，start：{start}，length：{length},MaxPDUSize:{_Plc.MaxPDUSize}");
                        }
                    }
                    catch (Exception ex)
                    {
                        stopwatch.Stop();
                        nlogUtilScoped.Fatal($"S7Plc读报文异常1:{Math.Round(stopwatch.Elapsed.TotalMilliseconds, 0)}毫秒，IP：{_Plc.IP}，DB：{DB}，start：{start}，length：{length},MaxPDUSize:{_Plc.MaxPDUSize}, ex:{ex.Message}");
                    }
                    isFinish = true;
                }));
                thread.Start();
                while (true)
                {
                    if (isFinish)
                    {
                        break;
                    }
                    if (DateTime.Now.Subtract(dt).TotalSeconds > timeout)
                    {
                        nlogUtilScoped.Fatal($"ReadDbForMonitorAsync超时，强制中断当前线程，并重新初始化plc实例，IP：{IP}，DB：{DB}，start：{start}，length：{length},MaxPDUSize:{_Plc.MaxPDUSize},CpuType:{_cpuType},Rack:{_rack},Slot:{_slot}");
                        thread.IsBackground = true;
                        thread.Interrupt();
                        DisConnect();
                        await ConnectAsync();
                        break;
                    }
                    Thread.Sleep(10);
                }
                stopwatch.Stop();
                return bytes;

            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                nlogUtilScoped.Fatal($"S7Plc读报文异常2:{stopwatch.Elapsed.TotalMilliseconds}，IP：{_Plc.IP}，DB：{DB}，start：{start}，length：{length},MaxPDUSize:{_Plc.MaxPDUSize}, ex:{ex.Message}");
            }
            return null;
        }
        public async Task<byte[]> ReadDbAsync(int DB, int start, int length)
        {
            if (!await ConnectAsync())
            {
                return null;
            }
            try
            {
                byte[] bytes = await _Plc.ReadBytesAsync(DataType.DataBlock, DB, start, length);
                return bytes;
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc读报文发生异常ReadDbAsync，IP：{_Plc.IP}，DB：{DB}，start：{start}，length：{length}, ex={ex.Message}");
            }
            return null;
        }


        /// <summary>
        /// 线体写入DB   允许重新写入5次  写入后读取数据 判断是否一致 一致则写入成功
        /// </summary>
        /// <param name="DB"></param>
        /// <param name="start"></param>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public async Task<bool> WriteDbAsync(int DB, int start, byte[] bytes, string equment = "")
        {
            if (!await ConnectAsync())
            {
                return false;
            }
            try
            {
                //允许重写5次
                var count = 1;
                while (true)
                {

                    //向DB写入数据
                    await _Plc.WriteBytesAsync(DataType.DataBlock, DB, start, bytes);

                    var bytesStr = "";
                    foreach (var byteItem in bytes)
                    {
                        bytesStr += byteItem.ToString() + ":";
                    }
                    bytesStr = bytesStr.TrimEnd(':');
                    _LogUtil.Debug($"设备【{equment}】写入PLC数据：【{bytesStr}】,DB块【{DB}】,起始位【{start}】，长度【{bytes.Length}】");
                    //延迟20毫秒
                    await Task.Delay(20);
                    //读取DB数据
                    var readDbTemp = await _Plc.ReadBytesAsync(DataType.DataBlock, DB, start, bytes.Length);

                    var readDbTempStr = "";
                    foreach (var byteItem in readDbTemp)
                    {
                        readDbTempStr += byteItem.ToString() + ":";
                    }
                    readDbTempStr = readDbTempStr.TrimEnd(':');
                    _LogUtil.Debug($"设备【{equment}】写入PLC后读取数据：【{readDbTempStr}】,DB块【{DB}】,起始位【{start}】，长度【{bytes.Length}】");
                    //判断是否一致  一致说明写进去了  不一致说明未写入成功
                    if (BytesExt.BytesEquals(bytes, readDbTemp))
                    {
                        return true;
                    }
                    else
                    {
                        count++;
                    }
                    if (count > 5)
                    {
                        _LogUtil.Error($"设备【{equment}】S7Plc写报文发生重写5次失败，IP：{_Plc.IP}，DB：{DB}，start：{start}");
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"设备【{equment}】S7Plc写报文发生异常WriteDbAsync ex= {ex.Message}，IP：{_Plc.IP}，DB：{DB}，start：{start}");
            }
            return false;
        }


        /// <summary>
        /// 堆垛机DB写入 DOTO由于写入堆垛机DB后堆垛机立马会有数据变化 所以不能直接就判断读取的数据是否写入成功
        /// </summary>
        /// <param name="DB"></param>
        /// <param name="start"></param>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public async Task<bool> WriteDbToSCAsync(int DB, int start, byte[] bytes)
        {
            if (!await ConnectAsync())
            {
                return false;
            }
            try
            {
                //向DB写入数据
                await _Plc.WriteBytesAsync(DataType.DataBlock, DB, start, bytes);

                var bytesStr = "";
                foreach (var byteItem in bytes)
                {
                    bytesStr += byteItem.ToString() + ":";
                }
                bytesStr = bytesStr.TrimEnd(':');
                _LogUtil.Debug($"写入堆垛机PLC数据：【{bytesStr}】,DB块【{DB}】,起始位【{start}】，长度【{bytes.Length}】");
                //延迟20毫秒
                await Task.Delay(20);
                //读取DB数据
                var readDbTemp = await _Plc.ReadBytesAsync(DataType.DataBlock, DB, start, bytes.Length);

                var readDbTempStr = "";
                foreach (var byteItem in readDbTemp)
                {
                    readDbTempStr += byteItem.ToString() + ":";
                }
                readDbTempStr = readDbTempStr.TrimEnd(':');
                _LogUtil.Debug($"写入堆垛机PLC后读取数据：【{readDbTempStr}】,DB块【{DB}】,起始位【{start}】，长度【{bytes.Length}】");
                return true;

            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc写报文发生异常WriteDbAsync，IP：{_Plc.IP}，DB：{DB}，start：{start}，异常信息：{ex.Message}");
            }
            return false;
        }

        /// <summary>
        /// 写一个点位
        /// </summary>
        /// <param name="point"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<bool> WritePointAsync(string point, object value)
        {
            if (!await ConnectAsync())
            {
                return false;
            }
            try
            {
                await _Plc.WriteAsync(point, value);
                return true;
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc写报文发生异常WritePointAsync，IP：{_Plc.IP}，DB：{point}，value：{value},异常:{ex}");
            }
            return false;

        }

        public async Task<bool> WriteDbBitAsync(int DB, int start, int bitAdr, bool value)
        {
            if (!IsConnected)
            {
                return false;
            }
            try
            {
                await _Plc.WriteBitAsync(DataType.DataBlock, DB, start, bitAdr, value);
                return true;
            }
            catch (Exception ex)
            {
                _LogUtil.Error($"S7Plc写报文发生异常WriteDbBitAsync，IP：{_Plc.IP}，DB：{DB}，start：{start},异常:{ex}");
            }
            return false;
        }

    }
}
